package tileStyle;

import java.io.*;
import java.util.*;

import tileStyle.Prism.*;

/**
 * @author Yuriy
 *
 */
public class TileStyleStarter {

	public static final boolean DEBUG = false;
	public static String JOB_NUM;
	/**
	 * @param args[0]   : "server" or "client"
	 * @param args[1]   : my hostname or IP address
	 * if args[0] is "server"
	 *   @param args[2] : the integer number of nodes to expect to sign in
	 *   @param args[3] : the full path to the tiles file
	 *   @param args[4] : the full path of a seed file 
	 *   @param args[5] : the full path to the output file
	 * if args[0] is "client"
	 *   @param args[2] : the sign in server hostname or IP address 
	 */
	public static void main(String[] args) throws IOException, TileStyleException {
		String myJob = args[0].trim();
		String myHostname = args[1].trim();

		Map<TileType, List<NodeAddress>> addresses = null;
		Map <String, TileType> tileNameLookup = null;
		
		/* 
		 * if I am the sign in server:
		 * 1. create a sign in server
		 * 2. read tile file to create a list of tile types
		 * 3. wait for everyone to sign in
		 * 4. create an addresses map
		 * 5. distribute the addresses map to everyone
		 */

		BufferedWriter outFile = null;

		SignInServer server = null;
		SignInClient client = null;
		if (myJob.equals("server")) {
			JOB_NUM = args[6].trim();
			int expectedHosts = Integer.parseInt(args[2].trim());
			String tileFilename = args[3].trim();
			//String seedFilename = args[4].trim();
			outFile = new BufferedWriter(new FileWriter(args[5].trim()));
			
			// Create a sign in server
			server = new SignInServer(myHostname, expectedHosts);
			
			// Read from a file and create the list types
			List<TileType> types = new ArrayList<TileType>();
			// Also create a map from input tile names to tile types for seed creation
			tileNameLookup = new HashMap<String, TileType>();

			// Reads .tiles file, format version 0.4
			// version 0.4:
			// every line either:
			// 1. starts with % (comment line) and is ignored.
			// 2. tilename,NORTH,EAST,SOUTH,WEST,VALUE
			// 		 tilename is a unique ID for the tile type
			// 		 all values in CAPITALS are ints
			BufferedReader tileFile = new BufferedReader(new FileReader(tileFilename));
			String line = tileFile.readLine();
			while (line != null) {
				StringTokenizer tokens = new StringTokenizer(line, ",", false);

				String name = tokens.nextToken();
				if (!(name.startsWith("%"))) {
					TileType type = new TileType(name, Integer.parseInt(tokens.nextToken()),
							Integer.parseInt(tokens.nextToken()),
							Integer.parseInt(tokens.nextToken()),
							Integer.parseInt(tokens.nextToken()),
							Integer.parseInt(tokens.nextToken()));
					types.add(type);
					tileNameLookup.put(name.trim(), type);
					if (tokens.hasMoreTokens())
						throw new IOException("Tile Type file " + tileFilename + " imporper number of , separated tokens on line starting with: " + name);
				}
				line = tileFile.readLine();
			}
			tileFile.close();
			
			System.out.println("waiting for sign in");
			
			// Now wait for everyone to sign in
			while (!(server.isReady())) {
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e); 
				}
			}
//			Thread.yield();
			
			System.out.println("server is ready");
			
			// Create an addresses map
			addresses = new HashMap<TileType, List<NodeAddress>>();
			
			List<String> hostnames = server.getHostnames();
			
			System.out.println("got " + hostnames.size() + " hostnames");
			
			// if there are at least as many hosts as types, just distribute the types around 
			if (expectedHosts >= types.size()) {
				for (int i = 0; i < hostnames.size(); i++) {
					NodeAddress currentAddress = new NodeAddress(hostnames.get(i), i, 2601);
					TileType currentType = types.get(i % types.size());
					List<NodeAddress> currentList = addresses.get(currentType);
					if (currentList == null)
						currentList = new ArrayList<NodeAddress>();
					currentList.add(currentAddress);
					addresses.put(currentType, currentList);
				}
			}
			else {
				// since there are fewer hosts than types, distribute with duplicates
				for (int i = 0; i < types.size(); i++) {
					TileType currentType = types.get(i);
					NodeAddress currentAddress = new NodeAddress(hostnames.get(i % hostnames.size()), 
							i, 2601 + ((int) i / hostnames.size()));
					List<NodeAddress> currentList = new ArrayList<NodeAddress>();
					currentList.add(currentAddress);
					addresses.put(currentType, currentList);
				}
			}
			
			// now add the nodeAddresses for setting up the seed
			int counter = 0;
			for (int i = 0; i < types.size(); i++) {
				TileType currentType = types.get(i);
				if (currentType.getName().startsWith("input")) {
					NodeAddress currentAddress = new NodeAddress(myHostname, types.size() + hostnames.size() + counter - 1, 2601 + types.size() + counter, true);
					List<NodeAddress> currentList = addresses.get(currentType);
					if (currentList == null)
						throw new TileStyleException("Creating a forSeed node but no node for this tile exists yet");
					currentList.add(currentAddress);
					addresses.put(currentType, currentList);
					counter++;
				}
			}

			System.out.println("now going to send the addresses map");
			
			// Now distribute the addresses map to all the clients
//			server.send(new AddressMapEvent("Addresses map compiled", addresses));
			server.sendAddressMap(addresses);
			
			System.out.println("sent the addresses map");
		}
		else if (myJob.equals("client")) {
			/* 
			 * if I am a client :
			 * 1. create a sign in client and sign in to the sign in server
			 * 2. wait for the addresses map to arrive and grab it
			 * 3. 4. 5. these steps are intentionally left blank
			 */ 
			
			String signInHostname = args[2].trim();
			
			// Create a sign in client and sign in to the sign in server
			client = new SignInClient(myHostname, signInHostname);
			
			// Now wait for the addresses map to arrive and grab it
			while (!(client.isReady())) {
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e); 
				}
			}
			
			addresses = client.getAddresses();
		}
		
		System.out.println("addresses ready");
		
		/*
		 * 6. use the addresses map to create local node objects
		 *    (modify the local NodeAddress objects to contain references to local Node objects
		 */
		
		// Now create the local Node objects and the addresses Map for all tile types
		List<Node> myNodes = Collections.synchronizedList(new ArrayList<Node>());

		for (Iterator<TileType> types = addresses.keySet().iterator(); types.hasNext();) {
			TileType currentType = types.next();
			List<NodeAddress> currentNodes = addresses.get(currentType);
			for (int i = 0; i < currentNodes.size(); i++) {
				NodeAddress currentNodeAddress = currentNodes.get(i);
				String currentHostname = currentNodeAddress.getHostname();
				if (myHostname.equals(currentHostname)) {
					int currentUniqueID = currentNodeAddress.getUniqueID();
					int currentPort = currentNodeAddress.getPort();
					Node currentNode = new Node("Node" + currentType, currentType, 
							currentHostname, currentUniqueID, currentPort, outFile); //put outfile here
					myNodes.add(currentNode);
					currentNodeAddress.setNode(currentNode);
				} 
				// otherwise just keep the node entry in the addresses map as is
			}
		}
		
		
		/*
		 * if I am the sign in server:
		 * 7. wait for all the clients to be "set" 
		 * 8. let everyone know everyone is set 
		 */
		if (myJob.equals("server")) {
			System.out.println("Waiting for clients to set");
			while (!(server.isSet())) {
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e); 
				}
			}
			
			server.tellEveryoneSet();
		}

		/*
		 * if I am a client:
		 * 7. send the server an acknowledgment that I am "set"
		 * 8. and wait for a response that everyone's set
		 */	
		if (myJob.equals("client")) {
			System.out.println("My nodes are created but not connected yet, so I'm set");
			client.set();
			
			while (!(client.isEveryoneSet())) {
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e); 
				}
			}
		}
		
		
		
		/*
		 * 8. create the connections to local and remote Node objects
		 */
 
		System.out.println("Created all the nodes and inPorts on " + myHostname + ", now will connect them");

		// Create the connections to local and remote Nodes
		for (int i = 0; i < myNodes.size(); i++) {
			if (myJob.equals("server")) 
				System.out.println("Creating connections for node #" + (i + 1) + " of " + myNodes.size());
			Node currentNode = myNodes.get(i);
			currentNode.createLookup(addresses);
		}

		System.out.println("all connections created on " + myHostname);
		
		
		/*
		 * if I am a server:
		 * 9. wait for the clients to be "go"
		 */
		if (myJob.equals("server")) {
			System.out.println("Waiting for clients to go");
			while (!(server.isGo())) {
				try {
					Thread.sleep(1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e); 
				}
			}
		}
		
		/*
		 * if I am a client:
		 * 9. send the server an acknowledgment that I am "go"
		 */
		if (myJob.equals("client")) {
			System.out.println("My nodes are created but not connected yet, so I'm set");
			client.go();
		}

		/*
		 * if I am the sign in server:
		 * 10. read the .seed file and deploy the seed
		 */
		if (myJob.equals("server")) {
			
			System.out.println("Reading seed file");
			// Read .seed file, format version 0.1
			String seedFilename = args[4].trim();

			BufferedReader seedFile = new BufferedReader(new FileReader(seedFilename));
			String line = seedFile.readLine();
			Tile previous = null;
			int turn = 0;
			while (line != null) {
				if (line.startsWith("TURN"))
					turn = 1;
				else if (!(line.startsWith("%"))) {
					TileType type = tileNameLookup.get(line.trim());
					List<NodeAddress> currentNodes = addresses.get(type);
					
					NodeAddress currentNodeAddress;
					int i = 0;
					do {
						currentNodeAddress = currentNodes.get(i);
						i++;
					}
					while (!(currentNodeAddress.isForSeed()));
					
					Node node = currentNodeAddress.getNode();
					
					Tile tile =  node.deployTile(type, 0, true);
					if (previous != null) {
						previous.setNeighbor(tile.getAddress(), TileType.EAST - turn);
						tile.setNeighbor(previous.getAddress(), TileType.WEST - turn);
					}
					tile.setStatus(Tile.READY_TO_REPLICATE);
					previous = tile;
				}
				line = seedFile.readLine();
			}
			seedFile.close();
			System.out.println("Seed deployed");
		}
		
		String message = "Starting computation at at " + System.currentTimeMillis();
		if (outFile != null) {
			try {
				outFile.write(message);
				outFile.newLine();
			} catch (IOException e) {
				throw new TileStyleException(e.getMessage());
			}
		}
		if (myJob.equals("server"))
			System.out.println(message);

		/*
		 * Set up complete.  
		 * 11. Run the tile style!
		 */
		
		// .execute() all the nodes
		while(true)
			for (int i = 0; i < myNodes.size(); i++)
				myNodes.get(i).execute();
	}

/*	private static void processUserCommand() throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		while(true) {
			System.out.println("Type R when all hosts are ready");
			String input = reader.readLine().trim();
			if (input.equals("R"))
				break;
			else if (input.equals("Quit"))
				System.exit(0);
		}

	}
*/	
}
